package jmpo.cls;

/**
 * Данный класс является внутренним классом пакета.
 * Используется только для демонстрации наследования.
 * 
 * Очень простой класс, который содержит один метод check.
 * Обратим внимание, что метод всегда возвращает false.
 */
class Checker {
    boolean check(long number) {
        return false;
    } 
}

/**
 * С помощью ключевого слова extends мы получаем все возможности 
 * другого класса, называемого базовым. Класс, определённый с ключевым словом
 * extends назыается выведеным или унаследованным.
 * Открытые компоненты (данные и методы) базового класса
 * могут быть вызваны на объекте унаследованного класса.
 * В данном случае вызов check на объектах InheritedChecker будет допустим
 * и будет всегда возвращать false.
 */
class InheritedChecker extends Checker {
}

/**
 * Если внутри наследного класса объявлен метод с точно такой же сигнатурой,
 * то он автоматически заменяет собой метод из базового класса.
 * Теперь на любом объекте OverridedChecker вызов метода check с параметром long
 * будет возвращать true.
 * 
 * Обратим внимание, что мы можем свободно добавлять перегруженные методы в класс!
 * Они не будет считаться переопределёнными (overriden)!
 * Это может привести к неожиданному поведению: 
 * если передать для объекта типа OverridedChecker в качестве параметра метода
 * check значение типа int, то результат будет false, а не true.
 * 
 * Чтобы можно было бысто диагностировать эту ошибку на этапе компиляции класса 
 * в байт-код, используйте аннотацию @Override. 
 * Если для метода, помеченного @Override не обнаружится метода с такой же 
 * сигнатурой в одном из базовых классов, то компиляция завершится с ошибкой.
 */
class OverridedChecker extends Checker {
    @Override
    boolean check(long number) {
        return true;
    }
    // @Override // раскомментируйте строчку для ошибки компиляции
    boolean check(int number) {
        return false;
    }
}

/**
 * Класс может унаследоваться только от одного класса с помощью ключевого слова
 * extends. Но сама глубина иерархии может быть сколь угодно большой.
 * Мы можем унаследоваться от класса, который сам унаследован от другого класса.
 */
class RangeChecker extends OverridedChecker {
    @Override
    boolean check(long number) {
        return max >= number && min <= number;
    }

    @Override
    boolean check(int number) {
        return check((long)number);
    }

    RangeChecker(long max, long min) {
        this.max = max;
        this.min = min;
    }

    private long max, min;
}

/**
 * Если мы не хотим, чтобы от нашего класса можно было наследоваться, то
 * мы указываем ключевое слово final.
 * Теперь попытка унаследоваться от класса RangeWithNameChecker приведёт к 
 * ошибке компиляции.
 */
final class RangeWithNameChecker extends RangeChecker {
    /**
     * Если мы хотим передать данные в конструктор базового класса,
     * то мы используем ключевое слово super.
     * Вызов конструктора базового класса должен быть первым вызовом 
     * в конструкторе наследника.
     */
    RangeWithNameChecker(long max, long min, String name) {
        super(max, min);
        this.name = name;
    }

    String getName() {
        return name;
    }

    private String name;
}

/**
 * Если мы хотим запретить переопределять только некоторые методы,
 * но хотим оставить возможность переопределять другие методы, то 
 * мы ставим ключевое слово final возле тех методов, которые мы ограничиваем.
 */
class ValueChecker extends Checker {
    @Override
    final boolean check(long number) {
        return number == value;
    }

    ValueChecker(long value) {
        this.value = value;
    }

    private long value;
}

class ValueCheckerWithName extends ValueChecker {
    /**
     * Если раскомментировать код ниже, то возникнет ошибка компиляции:
     * Cannot override the final method from ValueChecker
     */
    /*
    @Override
    boolean check(long number) {
        return number != value;
    }
    */

    ValueCheckerWithName(long value, String name) {
        super(value);
        this.name = name;
    }
    
    final String getName() {
        return name;
    }

    private String name;
}

/**
 * Начиная с Java 17 появилась дополнительная возможность огранчить наследование
 * только определённым набором классов.
 * Указанные классы должны быть определены в том же пакете или в том же модуле.
 */
sealed class CompositeChecker extends Checker
    permits AndChecker, OrChecker {
    CompositeChecker(Checker lha, Checker rha) {
        this.lha = lha;
        this.rha = rha;
    }
    /**
     * Пример использования полей protected.
     */
    protected Checker lha, rha;
}

final class AndChecker extends CompositeChecker {
    @Override
    boolean check(long number) {
        return lha.check(number) && rha.check(number);
    }

    AndChecker(Checker lha, Checker rha) {
        super(lha, rha);
    }
}

final class OrChecker extends CompositeChecker {
    @Override
    boolean check(long number) {
        return lha.check(number) || rha.check(number);
    }

    OrChecker(Checker lha, Checker rha) {
        super(lha, rha);
    }
}

public class SimpleInheritance {
    private static boolean simpleExtensionTest() {
        InheritedChecker ick = new InheritedChecker();
        return ick.check(100) == false;
    }

    private static boolean baseReferenceExtensionTest() {
        Checker ick = new InheritedChecker();
        return ick.check(100) == false;
    }

    private static boolean overridedMethodTest() {
        Checker ick = new OverridedChecker();
        return ick.check((long)100) == true;
    }

    private static boolean overloadWithOverrideTest() {
        OverridedChecker ock = new OverridedChecker();
        return ock.check((int)100) == false;
    }

    private static boolean superclassConstructorA() {
        Checker ck = new RangeWithNameChecker(100, -100, null);
        return ck.check(200) == false;
    }

    private static boolean superclassConstructorB() {
        Checker ck = new RangeWithNameChecker(100, -100, null);
        return ck.check(-200) == false;
    }

    private static boolean superclassConstructorC() {
        Checker ck = new RangeWithNameChecker(100, -100, null);
        return ck.check(-10) == true;
    }

    private static boolean sealedclassTestA() {
        ValueChecker a_value = new ValueChecker(100);
        ValueChecker b_value = new ValueChecker(200);
        CompositeChecker cck = new AndChecker(a_value, b_value);
        return cck.check(100) == false;
    }

    private static boolean sealedclassTestB() {
        ValueChecker a_value = new ValueChecker(100);
        ValueChecker b_value = new ValueChecker(200);
        CompositeChecker cck = new OrChecker(a_value, b_value);
        return cck.check(100) == true;
    }

    private static void test(boolean result) {
        if (result) {
            System.out.println("Ok!");
            return;
        }
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        test(simpleExtensionTest());
        test(baseReferenceExtensionTest());
        test(overridedMethodTest());
        test(overloadWithOverrideTest());
        test(superclassConstructorA());    
        test(superclassConstructorB()); 
        test(superclassConstructorC());
        test(sealedclassTestA());
        test(sealedclassTestB());
    }
}
